﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Forms;
using System.Net;
using TrafficRelay;
using ShortcutToolbox.Core;
using System.IO;

namespace TrafficStatisticsPlugin
{
    public partial class TrafficStatisticsForm : Form, ILangConnector
    {
        private int trafficLogSize = 60;

        private IRelay relay;
        private Traffic rawTrafficStatistics = new Traffic();
        private LinkedList<TrafficLog> trafficLogList = new LinkedList<TrafficLog>();
        private List<string> leftHistory = new List<string>();
        private List<string> rightHistory = new List<string>();

        public TrafficStatisticsForm()
        {
            InitializeComponent();

            string[] items = new string[30];
            dynamic dylang = I18N.Current;
            for (int i = 0; i < 30; i++)
            {
                items[i] = dylang.TrafficStatisticsPlugin.TrafficStatisticsForm.
                    ChartComboBoxItem.ToString().Replace("{$value}", (i + 1).ToString());
            }
            ChartComboBox.Items.AddRange(items);

            InitTrafficHistoricalList();

            I18N.Changed += OnLangeChanged;
            OnLangeChanged(this, new LanguageChangedEventArgs(I18N.Current));
        }

        public void OnLangeChanged(object sender, LanguageChangedEventArgs e)
        {
            dynamic dylang = e.Value;
            Text = dylang.TrafficStatisticsPlugin.TrafficStatisticsForm.Title;
            ControlPanelGroupBox.Text = dylang.TrafficStatisticsPlugin.TrafficStatisticsForm.ControlPanelGroupBox;
            TrafficGroupBox.Text = dylang.TrafficStatisticsPlugin.TrafficStatisticsForm.TrafficGroupBox;
            LogGroupBox.Text = dylang.TrafficStatisticsPlugin.TrafficStatisticsForm.LogGroupBox;
            ProtocolLabel.Text = dylang.TrafficStatisticsPlugin.TrafficStatisticsForm.ProtocolLabel;
            LeftLabel.Text = dylang.TrafficStatisticsPlugin.TrafficStatisticsForm.LeftLabel;
            RightLabel.Text = dylang.TrafficStatisticsPlugin.TrafficStatisticsForm.RightLabel;
            ChartLabel.Text = dylang.TrafficStatisticsPlugin.TrafficStatisticsForm.ChartLabel;
            TopMostCheckBox.Text = dylang.TrafficStatisticsPlugin.TrafficStatisticsForm.TopMostCheckBox;
            StartButton.Text = dylang.TrafficStatisticsPlugin.TrafficStatisticsForm.StartButton;
            ResetButton.Text = dylang.TrafficStatisticsPlugin.TrafficStatisticsForm.ResetButton;
            StopButton.Text = dylang.TrafficStatisticsPlugin.TrafficStatisticsForm.StopButton;
            CleanLogsButton.Text = dylang.TrafficStatisticsPlugin.TrafficStatisticsForm.CleanLogsButton;
            InboundLabel.Text = dylang.TrafficStatisticsPlugin.TrafficStatisticsForm.InboundLabel;
            OutboundLabel.Text = dylang.TrafficStatisticsPlugin.TrafficStatisticsForm.OutboundLabel;
        }

        private void StatisticsForm_Load(object sender, EventArgs e)
        {
            ChartComboBox.SelectedIndex = 0;

            TopMostCheckBox.Checked = TopMost;
            TypeComboBox.SelectedIndex = 0;

            LoadHistoryAddress();
            LeftAddressTextBox.Items.AddRange(leftHistory.ToArray());
            RightTextBox.Items.AddRange(rightHistory.ToArray());
            if (leftHistory.Count > 0)
                LeftAddressTextBox.Text = leftHistory[0];
            if (rightHistory.Count > 0)
                RightTextBox.Text = rightHistory[0];

            timer1.Start();
            timer2.Start();
        }

        private void UpdateHistoryAddress()
        {
            LeftAddressTextBox.Items.Clear();
            RightTextBox.Items.Clear();
            LeftAddressTextBox.Items.AddRange(leftHistory.ToArray());
            RightTextBox.Items.AddRange(rightHistory.ToArray());
        }

        private void LoadHistoryAddress()
        {
            leftHistory = LoadHistory(Utils.GetAppDataFilePath("TrafficStatistics-history-left.txt"));
            rightHistory = LoadHistory(Utils.GetAppDataFilePath("TrafficStatistics-history-right.txt"));
        }

        private void SaveHistoryAddress()
        {
            SaveHistory(Utils.GetAppDataFilePath("TrafficStatistics-history-left.txt"), leftHistory);
            SaveHistory(Utils.GetAppDataFilePath("TrafficStatistics-history-right.txt"), rightHistory);
        }

        protected override void OnClosing(CancelEventArgs e)
        {
            try
            {
                timer1.Enabled = timer2.Enabled = false;
                timer1.Stop();
                timer2.Stop();
                Stop();
            }
            catch(Exception ex)
            {
                Logging.LogUsefulException(ex);
            }
            base.OnClosing(e);
        }

        protected override void OnClosed(EventArgs e)
        {
            I18N.Changed -= OnLangeChanged;
            base.OnClosed(e);
        }

        private void ChartComboBox_SelectedIndexChanged(object sender, EventArgs e)
        {
            trafficLogSize = (ChartComboBox.SelectedIndex + 1) * 60;
        }

        private void StartButton_Click(object sender, EventArgs e)
        {
            try
            {
                Start(LeftAddressTextBox.Text, RightTextBox.Text, TypeComboBox.SelectedItem.ToString() == "UDP");
            }
            catch(Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

        private void StopButton_Click(object sender, EventArgs e)
        {
            try
            {
                Stop();
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

        private void TopMostheckBox_CheckedChanged(object sender, EventArgs e)
        {
            TopMost = TopMostCheckBox.Checked;
        }

        private void ResetButton_Click(object sender, EventArgs e)
        {
            rawTrafficStatistics.reset();
            InitTrafficHistoricalList();
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            UpdateTrafficStatistics();
        }

        private void timer2_Tick(object sender, EventArgs e)
        {
            try
            {
                timer2.Stop();
                UpdateTrafficList();
                if (TrafficChart.InvokeRequired)
                {
                    TrafficChart.Invoke(new EventHandler((sender2, e2) =>
                    {
                        UpdateTrafficChart();
                    }), sender, e);
                }
                else
                {
                    UpdateTrafficChart();
                }
            }
            catch (Exception ex)
            {
                string s = ex.Message;
            }
            finally
            {
                timer2.Start();
            }
        }

        private void CleanLogsButton_Click(object sender, EventArgs e)
        {
            LogTextBox.Text = "";
        }

        private EndPoint ParseEndPoint(string address)
        {
            string[] addr_compns = address.Split(':');
            IPAddress ipAddress;
            if (string.IsNullOrEmpty(addr_compns[0]))
                ipAddress = IPAddress.Any;
            else if (!IPAddress.TryParse(addr_compns[0], out ipAddress))
                ipAddress = Dns.GetHostEntry(addr_compns[0]).AddressList[0];
            IPEndPoint ep = new IPEndPoint(ipAddress, Convert.ToInt32(addr_compns[1]));
            return ep;
        }

        private void Start(string leftAddr, string rightAddr, bool udp)
        {
            EndPoint left = ParseEndPoint(leftAddr);
            EndPoint right = ParseEndPoint(rightAddr);
            relay = udp ? (IRelay)new UDPRelay(left, right) : (IRelay)new TCPRelay(left, right);
            relay.Inbound += Relay_Inbound;
            relay.Outbound += Relay_Outbound;
            relay.Error += Relay_Error;
            relay.Start();

            if (leftHistory.Contains(left.ToString()))
                leftHistory.Remove(left.ToString());
            leftHistory.Insert(0, left.ToString());

            if (rightHistory.Contains(right.ToString()))
                rightHistory.Remove(right.ToString());
            rightHistory.Insert(0, right.ToString());

            UpdateHistoryAddress();
            LeftAddressTextBox.Text = left.ToString();
            RightTextBox.Text = right.ToString();
            SaveHistoryAddress();

            TypeComboBox.Enabled = false;
            LeftAddressTextBox.Enabled = false;
            RightTextBox.Enabled = false;
            StartButton.Enabled = false;
            StopButton.Enabled = true;

            AppendLog(I18N.Current.TrafficStatisticsPlugin.Log.Starting.ToString()
                .Replace("{$protocol}", (udp ? "UDP" : "TCP"))
                .Replace("{$left}", left.ToString())
                .Replace("{$right}", right.ToString()) + "\n");
        }

        private void Stop()
        {
            if (relay != null)
            {
                relay.Stop();
                relay = null;
            }

            TypeComboBox.Enabled = true;
            LeftAddressTextBox.Enabled = true;
            RightTextBox.Enabled = true;
            StartButton.Enabled = true;
            StopButton.Enabled = false;

            AppendLog($"{I18N.Current.TrafficStatisticsPlugin.Log.Stopping}\n");
        }

        private void Relay_Inbound(object sender, RelayEventArgs e)
        {
            rawTrafficStatistics.onInbound(e.Value);
        }

        private void Relay_Outbound(object sender, RelayEventArgs e)
        {
            rawTrafficStatistics.onOutbound(e.Value);
        }

        private void Relay_Error(object sender, RelayErrorEventArgs e)
        {
            if (e.Error != null)
                AppendLog(e.Error.ToString() + "\n");
        }

        private void AppendLog(string line)
        {
            if (LogTextBox.InvokeRequired)
            {
                LogTextBox.Invoke(new EventHandler((s, e) => {
                    LogTextBox.AppendText(line);
                    LogTextBox.ScrollToCaret();
                }), this, null);
            }
            else
            {
                LogTextBox.AppendText(line);
                LogTextBox.ScrollToCaret();
            }
        }

        private void InitTrafficHistoricalList()
        {
            lock(trafficLogList)
            {
                trafficLogList.Clear();
                for (int i = 0; i < trafficLogSize; i++)
                {
                    trafficLogList.AddLast(new TrafficLog());
                }
            }
        }

        private void UpdateTrafficList()
        {
            lock (trafficLogList)
            {
                TrafficLog previous = trafficLogList.Last.Value;
                TrafficLog current = new TrafficLog(
                    new Traffic(rawTrafficStatistics),
                    new Traffic(rawTrafficStatistics, previous.raw)
                );
                trafficLogList.AddLast(current);

                while (trafficLogList.Count > trafficLogSize) trafficLogList.RemoveFirst();
                while (trafficLogList.Count < trafficLogSize) trafficLogList.AddFirst(new TrafficLog());
            }
        }

        private List<float> rawInboundPoints = new List<float>();
        private List<float> rawOutboundPoints = new List<float>();
        private string[] units = new string[] { "B", "KiB", "MiB", "GiB" };

        private void UpdateTrafficChart()
        {
            TrafficLog last;
            long maxSpeedValue = 0;
            rawInboundPoints.Clear();
            rawOutboundPoints.Clear();
            lock(trafficLogList)
            {
                last = trafficLogList.Last.Value;
                foreach (TrafficLog item in trafficLogList)
                {
                    rawInboundPoints.Add(item.rawSpeed.inbound);
                    rawOutboundPoints.Add(item.rawSpeed.outbound);

                    maxSpeedValue = Math.Max(maxSpeedValue,
                            Math.Max(item.rawSpeed.inbound, item.rawSpeed.outbound)
                    );
                }
            }

            FormattedSize maxSpeed = new FormattedSize(maxSpeedValue);
            RawInboundSpeed.Text = new FormattedSize(last.rawSpeed.inbound) + "/s";
            RawOutboundSpeed.Text = new FormattedSize(last.rawSpeed.outbound) + "/s";

            for (int i = 0; i < rawInboundPoints.Count; i++)
            {
                rawInboundPoints[i] /= maxSpeed.scale;
                rawOutboundPoints[i] /= maxSpeed.scale;
            }

            TrafficChart.Series[0].LegendText = I18N.Current.TrafficStatisticsPlugin.Chart.Legend.Inbound;
            TrafficChart.Series[0].Name = I18N.Current.TrafficStatisticsPlugin.Chart.Legend.Inbound;
            TrafficChart.Series[0].Points.DataBindY(rawInboundPoints);
            TrafficChart.Series[0].ToolTip = "#SERIESNAME #VALY{F2} " + maxSpeed.unit + "/s";
            TrafficChart.Series[1].LegendText = I18N.Current.TrafficStatisticsPlugin.Chart.Legend.Outbound;
            TrafficChart.Series[1].Name = I18N.Current.TrafficStatisticsPlugin.Chart.Legend.Outbound;
            TrafficChart.Series[1].Points.DataBindY(rawOutboundPoints);
            TrafficChart.Series[1].ToolTip = "#SERIESNAME #VALY{F2} " + maxSpeed.unit + "/s";
            TrafficChart.ChartAreas[0].AxisY.LabelStyle.Format = "{0:0.##} " + maxSpeed.unit + "/s";

        }

        private void UpdateTrafficStatistics()
        {
            RawInbound.Text = new FormattedSize(rawTrafficStatistics.inbound).ToString();
            RawOutbound.Text = new FormattedSize(rawTrafficStatistics.outbound).ToString();
        }

        private List<string> LoadHistory(string file)
        {
            List<string> list = new List<string>();

            if (File.Exists(file))
            {
                using (StreamReader sr = new StreamReader(file))
                {
                    string line;
                    while ((line = sr.ReadLine()) != null)
                    {
                        if (!string.IsNullOrWhiteSpace(line))
                            list.Add(line.Trim());
                    }
                }
            }
            return list;
        }

        private void SaveHistory(string file, List<string> list)
        {
            File.WriteAllLines(file, list.ToArray());
        }

        class FormattedSize
        {
            public long rawValue;

            public long scale;
            public float value;
            public string unit;

            public FormattedSize() { }

            public FormattedSize(long n)
            {
                FormatSize(n);
            }

            public void FormatSize(long n)
            {
                rawValue = n;
                long scale = 1;
                float f = n;
                string unit = "B";
                if (f > 1024)
                {
                    f = f / 1024;
                    scale <<= 10;
                    unit = "KiB";
                }
                if (f > 1024)
                {
                    f = f / 1024;
                    scale <<= 10;
                    unit = "MiB";
                }
                if (f > 1024)
                {
                    f = f / 1024;
                    scale <<= 10;
                    unit = "GiB";
                }
                if (f > 1024)
                {
                    f = f / 1024;
                    scale <<= 10;
                    unit = "TiB";
                }
                this.scale = scale;
                this.value = f;
                this.unit = unit;
            }

            public override string ToString()
            {
                return value.ToString("F2") + " " + unit;
            }

        }
    }
}
